﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DNADataHiding.Exceptions;

namespace DNADataHiding.Model.Encryption
{
    public class InsertionEncryptor : IEncryptor
    {
        private int _messageSeed;
        private int _sequenceSeed;

        public InsertionEncryptor(int messageSeed, int sequenceSeed)
        {
            _messageSeed = messageSeed;
            _sequenceSeed = sequenceSeed;
        }


        private IList<string> SplitMessage(string message)
        {
            Random randomMessage = new Random(_messageSeed);

            IList<string> retValue = new List<string>();

            int nextChunkSize = randomMessage.Next(1, 9);
            int totalSize = nextChunkSize;
            int startIndex = 0;
            
            while (totalSize < message.Length)
            {
                retValue.Add(message.Substring(startIndex, nextChunkSize));

                startIndex += nextChunkSize;
                nextChunkSize = randomMessage.Next(1, 9);
                totalSize += nextChunkSize;
            }

            retValue.Add(message.Substring(startIndex));

            return retValue;
        }

        private IList<string> SplitSequence(string binarySequence, int max)
        {
            Random randomSequence = new Random(_sequenceSeed);

            IList<string> retValue = new List<string>();

            int nextChunkSize = randomSequence.Next(1, 9);
            int totalSize = nextChunkSize;
            int startIndex = 0;

            while ((totalSize < binarySequence.Length) && (retValue.Count < max))
            {
                retValue.Add(binarySequence.Substring(startIndex, nextChunkSize));

                startIndex += nextChunkSize;
                nextChunkSize = randomSequence.Next(1, 9);
                totalSize += nextChunkSize;
            }

            if (retValue.Count < max)
                retValue.Add(binarySequence.Substring(startIndex));

            return retValue;
            
        }

        public DNASequence Encrypt(string message, DNASequence baseSequence)
        {
            string binaryMessage = EncryptionUtils.GetBinaryMessage(message);
            string binarySequence = baseSequence.ToBinary();

            IList<string> splittedMessage = SplitMessage(binaryMessage);
            IList<string> splittedSequence = SplitSequence(binarySequence, splittedMessage.Count - 1);

            //splitted message tiene t elementos y splittedsequence tiene que tener t-1, sino, throw
            //sequence se supone sea mucho mas largo

            StringBuilder fakeSequence = new StringBuilder();

            for (int i = 0; i < splittedSequence.Count; i++)
            {
                fakeSequence.Append(splittedMessage[i]);
                fakeSequence.Append(splittedSequence[i]);
            }

            fakeSequence.Append(splittedMessage.Last());

            //fix para impares
            if ((fakeSequence.ToString().Length % 2) != 0)
            {
                fakeSequence.Append("0");
            }
            
            return DNASequence.FromBinary(fakeSequence.ToString());
        }

        public string Decrypt(IList<DNASequence> sequences, DNASequence baseSequence)
        {
            foreach (DNASequence fakeSequence in sequences)
            {
                Random randomMessage = new Random(_messageSeed);
                Random randomSequence = new Random(_sequenceSeed);

                string binarySequence = fakeSequence.ToBinary();

                IList<string> messageChunks = new List<string>();
                IList<string> sequenceChunks = new List<string>();

                int nextSeqChunkSize = randomSequence.Next(1, 9);
                int nextMsgChunkSize = randomMessage.Next(1, 9);

                int totalSize = nextSeqChunkSize + nextMsgChunkSize;
                int startIndex = 0;

                while (totalSize < binarySequence.Length)
                {
                    messageChunks.Add(binarySequence.Substring(startIndex, nextMsgChunkSize));
                    sequenceChunks.Add(binarySequence.Substring(startIndex + nextMsgChunkSize, nextSeqChunkSize));

                    startIndex += nextSeqChunkSize + nextMsgChunkSize;

                    nextSeqChunkSize = randomSequence.Next(1, 9);
                    nextMsgChunkSize = randomMessage.Next(1, 9);

                    totalSize += nextSeqChunkSize + nextMsgChunkSize;
                }

                messageChunks.Add(binarySequence.Substring(startIndex));

                StringBuilder testSequence = new StringBuilder();
                foreach (string sequenceChunk in sequenceChunks)
                {
                    testSequence.Append(sequenceChunk);
                }

                string sequenceToEvaluate = testSequence.ToString();
                //fix para impares
                if (sequenceToEvaluate.Count()%2 != 0)
                {
                    sequenceToEvaluate = sequenceToEvaluate.Remove(sequenceToEvaluate.Count() - 1, 1);
                }

                if (DNASequence.FromBinary(sequenceToEvaluate).IsPrefixOf(baseSequence))
                {
                    StringBuilder message = new StringBuilder();
                    foreach (string messageChunk in messageChunks)
                    {
                        message.Append(messageChunk);
                    }

                    string messageToRead = message.ToString();

                    //fix para impares
                    if (messageToRead.Length%2 != 0)
                    {
                        messageToRead = messageToRead.Remove(messageToRead.Count() - 1, 1);
                    }

                    return EncryptionUtils.GetMessageFromBinary(messageToRead);
                }
            }

            //no encontro ninguna que sea prefijo
            throw new ModelException("Ninguna de las cadenas es prefijo de la cadena base");
        }


        public bool NeedsRandomListOfSequences()
        {
            return true;
        }


        public int ExtraEncryptionOutput
        {
            get
            {
                return 0;
            }
        }
    }
}
